﻿Imports System.Reflection
Imports System.Text
Imports Kairyu.Extension

Namespace Load

    ''' <summary>
    ''' ルートテーブルエイリアス
    ''' </summary>
    ''' <remarks></remarks>
    Public Class RootTableAlias
        Inherits TableAlias

        ''' <summary>
        ''' 上層カスケードの上限数を示します
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>
        ''' -1の場合、上限はありません
        ''' 0の場合、上層カスケードをせず、ルートのみ読み込みします
        ''' </remarks>
        Public Property LimitLevel As Integer = 0

        ''' <summary>
        ''' 読み込み無効プロパティ
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>
        ''' 子の後付読み込み処理用です
        ''' 子に存在する親関連プロパティをセットします
        ''' プロパティが指定されている場合、同プロパティへのカスケードはしません
        ''' </remarks>
        Public Property IgnoreProperty As PropertyInfo = Nothing

        ''' <summary>
        ''' 外部結合を許容するかどうかを示します
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property CanLeftJoin As Boolean = False

        ''' <summary>
        ''' パラメータ接頭語
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property ParameterPrefix As String

        ''' <summary>
        ''' 全ての列エイリアス（キャッシュ値）
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Property AllColumnAliases As List(Of ColumnAlias)

        Public Overrides Sub Build(creater As System.Func(Of TableAlias, System.Reflection.PropertyInfo, Integer, RelationTableAlias), register As System.Func(Of TableAlias, Integer))
            MyBase.Build(creater, register)
            '処理終了後、列をキャッシュ
            Me.AllColumnAliases = Me.GetAllColumnAliases
        End Sub

#Region "Build SQL"
        ''' <summary>
        ''' コマンドを作成します
        ''' </summary>
        ''' <param name="cn"></param>
        ''' <param name="prmFac"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function CreateCommand(cn As IDbConnection, prmFac As IQuery) As IDbCommand
            'テーブルを再登録し、ユニークなエイリアスIDを発行します。
            Me.Build()

            Dim cmd As IDbCommand = cn.CreateCommand
            Dim s As New StringBuilder
            s.Append(Me.GetSelectText & Me.GetFromText)

            If prmFac IsNot Nothing AndAlso prmFac.ParameterBuilders.Any Then
                Dim whereText As New StringBuilder

                Dim nameReader As Func(Of MemberInfo, String) =
                    Function(m)
                        Dim col As ColumnAttribute = ColumnAttributeReader.Read(m).First
                        Return Me.GetColumnAlias(col).FullName
                    End Function

                Dim pcnt As Integer = 0
                Dim prmRegister As Func(Of Object, String) =
                    Function(val)
                        Dim prm As IDbDataParameter = cmd.CreateParameter
                        prm.ParameterName = "P" & pcnt
                        pcnt += 1
                        prm.Value = val
                        cmd.Parameters.Add(prm)
                        Return Me.ParameterPrefix & prm.ParameterName
                    End Function

                Dim whereRegiser As Action(Of String) =
                    Sub(txt)
                        whereText.AppendDelimiter(" AND ")
                        whereText.Append(txt)
                    End Sub

                prmFac.BuildParameter(nameReader, prmRegister, whereRegiser)

                s.Append(" WHERE " & whereText.ToString)
            End If
            cmd.CommandText = s.ToString

            Return cmd
        End Function

        ''' <summary>
        ''' カスケード情報を再生成します
        ''' </summary>
        ''' <remarks></remarks>
        Private Overloads Sub Build()
            Dim id As Integer = 0

            'リスト登録し、IDを発番します。
            Dim register As Func(Of TableAlias, Integer) =
                (Function(item As TableAlias)
                     Dim current As Integer = id
                     id += 1
                     Return current
                 End Function)

            '標準のテーブルエイリアス生成
            Dim createrAsNormal As Func(Of TableAlias, PropertyInfo, Integer, RelationTableAlias) =
                (Function(base As TableAlias, prop As PropertyInfo, level As Integer)
                     '上限チェック
                     If Me.LimitLevel <> -1 AndAlso Me.LimitLevel < level Then Return Nothing

                     '無効プロパティチェック
                     If prop.Equals(Me.IgnoreProperty) Then Return Nothing

                     '自己参照チェック（自己参照は無限ループするので、上限なしを指定しても、上限1として処理されます。）
                     If prop.PropertyType.Equals(prop.DeclaringType) AndAlso Me.LimitLevel = -1 AndAlso level <> 1 Then Return Nothing

                     '生成処理
                     Dim als As New RelationTableAlias With {.CascadeLevel = level}
                     als.ActualTableAlias = als
                     als.Table = prop.PropertyType.ToTable

                     For Each rel As IParentRelation In (From el In base.Table.Columns Where el.PropertyInfo.Equals(prop) Select DirectCast(el, IParentRelation))
                         als.Relations.Add(rel)
                     Next

                     '外部結合許可チェック
                     If Me.CanLeftJoin = False AndAlso als.Relations.First.IsNullable Then Return Nothing

                     Return als
                 End Function)

            Dim createrAsBase As Func(Of TableAlias, RelationTableAlias) =
                (Function(myTable As TableAlias)
                     '生成処理
                     Dim als As New RelationTableAlias With {.CascadeLevel = myTable.CascadeLevel}
                     als.ActualTableAlias = myTable.ActualTableAlias
                     als.Table = myTable.Table.ModelType.BaseType.ToTable

                     als.Relations = myTable.Table.SuperRelations

                     Return als
                 End Function)

            'プロパティから親テーブルエイリアスを作成します
            Dim creater As Func(Of TableAlias, PropertyInfo, Integer, RelationTableAlias) = Function(base As TableAlias, prop As PropertyInfo, level As Integer) If(prop Is Nothing, createrAsBase.Invoke(base), createrAsNormal.Invoke(base, prop, level))

            'ビルドします
            Me.Build(creater, register)
        End Sub

        ''' <summary>
        ''' SELECT句の取得
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function GetSelectText() As String
            Dim s As New StringBuilder

            '自身の列
            For Each item As ColumnAlias In Me.ColumnAliases
                s.AppendDelimiter(", ")
                s.Append(item.SelectCommandText)
            Next

            '上層へカスケード
            For Each item As RelationTableAlias In Me.RelationTableAliases
                '#96　継承元に独自列がない場合、SELECT文が正しく作成されたない不具合を修正。
                Dim txt As String = item.GetSelectText
                If String.IsNullOrEmpty(txt) Then Continue For
                s.AppendDelimiter(", ")
                s.Append(txt)
            Next

            Return String.Format("SELECT {0}", s.ToString)
        End Function

        ''' <summary>
        ''' FROM句の取得
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function GetFromText() As String
            '自身のテーブル
            Dim s As New StringBuilder
            s.AppendFormat(" FROM {0} {1}", Me.LoadTableFullName, Me.TableAliasFullName)

            '上層へカスケード
            For Each item As RelationTableAlias In Me.RelationTableAliases
                s.Append(item.GetFromText)
            Next

            Return s.ToString
        End Function

#End Region

        ''' <summary>
        ''' Nullを許容するかを示します
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides ReadOnly Property IsNullable As Boolean
            Get
                Return False
            End Get
        End Property

        ''' <summary>
        ''' 列エイリアスを取得します
        ''' </summary>
        ''' <param name="col"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function GetColumnAlias(col As ColumnAttribute) As ColumnAlias
            Dim q As IEnumerable(Of ColumnAlias) = Nothing

            Dim root As ColumnAttribute = col.GetRelationRootColumn

            '#75  「基底クラスにあるプロパティを検索条件に指定するとエラーが出る」Fix
            '#103 「親－子－孫の関係時、子、孫を起点とした読み込みができない」Fix
            q = (From el In Me.AllColumnAliases Where el.Column.GetRelationRootColumn.ColumnEquals(root))

            If q.Any = False Then
                Throw New InvalidOperationException(String.Format("列エイリアスが見つかりませんでした。型:{0}、列:{1}", Me.Table.ModelType.Name, col.ColumnName))
            End If

            If q.Count <> 1 Then
                '#103 「親－子－孫の関係時、子、孫を起点とした読み込みができない」Fix
                'ルートテーブルを優先する
                q = (From el In q Where el.TableAlias.IsRoot)
            End If

            If q.Count <> 1 Then
                Throw New InvalidOperationException(String.Format("検索条件の列が複数見つかりました。型:{0}、列:{1}", Me.Table.ModelType.Name, col.ColumnName))
            End If

            Return q.First
        End Function

    End Class

End Namespace
